/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.security;

/**
 * An implementation of the UUID class found in the java.util package
 * <p>
 * <b>Generating a Universally Unique ID (UUID)</b><br/> <i><font
 * color="green">//A Random UUID</font></i><br/> UUID
 * uid=UUID.getRandomUUID();<br/> <i><font color="green">//From an array of
 * bytes </font></i><br/> byte[] data=new
 * String("Test of uuid").getBytes();<br/> UUID
 * uid=UUID.nameUUIDFromBytes(data);
 * </p>
 * 
 * @author kentelt
 * 
 */
public class UUID {

	private static final long serialVersionUID = 0xbc9903f7986d852fL;

	private static String digits(long l, int i) {
		long l1 = 1L << i * 4;
		return Long.toString((l1 | l & l1 - 1L), 16).substring(1);
	}

	/**
	 * Creates a UUID from the string standard representation as described in
	 * the toString() method.
	 * 
	 * @param s
	 * @return
	 */
	public static UUID fromString(String s) {
		String as[] = com.aphrodite.util.StringUtil.split(s, '-', true);
		if (as.length != 5)
			throw new IllegalArgumentException((new StringBuffer()).append("Invalid UUID string: ").append(s)
					.toString());
		for (int i = 0; i < 5; i++)
			as[i] = (new StringBuffer()).append("0x").append(as[i]).toString();

		long l = Long.parseLong(as[0], isHex(as[0]) ? 16 : 10);// .longValue();
		l <<= 16;
		l |= Long.parseLong(as[1], isHex(as[1]) ? 16 : 10);// .longValue();
		l <<= 16;
		l |= Long.parseLong(as[2], isHex(as[2]) ? 16 : 10);// .longValue();
		long l1 = Long.parseLong(as[3], isHex(as[3]) ? 16 : 10);
		l1 <<= 48;
		l1 |= Long.parseLong(as[4], isHex(as[4]) ? 16 : 10);
		return new UUID(l, l1);
	}

	public static UUID getRandomUUID() {
		byte abyte0[] = new byte[16];
		java.util.Random rand = new java.util.Random();
		int i = 0;
		for (int j = abyte0.length; i < j;) {
			int k = rand.nextInt();
			int l = Math.min(j - i, 4);
			while (l-- > 0) {
				abyte0[i++] = (byte) k;
				k >>= 8;
			}
		}

		abyte0[6] &= 0xf;
		abyte0[6] |= 0x40;
		abyte0[8] &= 0x3f;
		abyte0[8] |= 0x80;
		return new UUID(abyte0);

	}

	private static boolean isHex(String value) {
		char[] data = value.toCharArray();
		for (int i = 0; i < data.length; i++) {
			if (com.aphrodite.util.CharUtil.isDigit(data[i])) {
				continue;
			} else {
				return true;
			}
		}
		return false;
	}

	public static UUID nameUUIDFromBytes(byte abyte0[]) {
		MD5 md5 = new MD5(abyte0);
		byte abyte1[] = md5.Final();
		abyte1[6] &= 0xf;
		abyte1[6] |= 0x30;
		abyte1[8] &= 0x3f;
		abyte1[8] |= 0x80;
		return new UUID(abyte1);
	}

	private transient int hashCode;
	private final long leastSigBits;
	private final long mostSigBits;

	private transient long node;

	private transient int sequence;

	private volatile transient long timestamp;

	private transient int variant;

	private transient int version;

	private UUID(byte abyte0[]) {
		version = -1;
		variant = -1;
		timestamp = -1L;
		sequence = -1;
		node = -1L;
		hashCode = -1;
		long l = 0L;
		long l1 = 0L;
		if (abyte0.length != 16)
			throw new IllegalArgumentException("Invalid byte length");
		for (int i = 0; i < 8; i++)
			l = l << 8 | (abyte0[i] & 0xff);

		for (int j = 8; j < 16; j++)
			l1 = l1 << 8 | (abyte0[j] & 0xff);

		mostSigBits = l;
		leastSigBits = l1;
	}

	/**
	 * Constructs a new UUID using the specified data. mostSigBits is used for
	 * the most significant 64 bits of the UUID and leastSigBits becomes the
	 * least significant 64 bits of the UUID.
	 * 
	 * @param mostSigBits
	 * @param leastSigBits
	 */
	public UUID(long mostSigBits, long leastSigBits) {
		version = -1;
		variant = -1;
		timestamp = -1L;
		sequence = -1;
		node = -1L;
		hashCode = -1;
		this.mostSigBits = mostSigBits;
		this.leastSigBits = leastSigBits;
	}

	public int clockSequence() throws IllegalArgumentException {
		if (version() != 1)
			throw new IllegalArgumentException("Not a time-based UUID");
		if (sequence < 0)
			sequence = (int) ((leastSigBits & 0x3fff000000000000L) >>> 48);
		return sequence;
	}

	public int compareTo(UUID uuid) {
		return mostSigBits >= uuid.mostSigBits ? mostSigBits <= uuid.mostSigBits ? leastSigBits >= uuid.leastSigBits ? ((byte) (((byte) (leastSigBits <= uuid.leastSigBits ? 0
				: 1))))
				: -1
				: 1
				: -1;
	}

	public long getLeastSignificantBits() {
		return leastSigBits;
	}

	public long getMostSignificantBits() {
		return mostSigBits;
	}

	public int hashCode() {
		if (hashCode == -1)
			hashCode = (int) (mostSigBits >> 32 ^ mostSigBits ^ leastSigBits >> 32 ^ leastSigBits);
		return hashCode;
	}

	public long timestamp() throws IllegalArgumentException {
		if (version() != 1)
			throw new IllegalArgumentException("Not a time-based UUID");
		long l = timestamp;
		if (l < 0L) {
			l = (mostSigBits & 4095L) << 48;
			l |= (mostSigBits >> 16 & 65535L) << 32;
			l |= mostSigBits >>> 32;
			timestamp = l;
		}
		return l;
	}

	public String toString() {
		return (new StringBuffer()).append(digits(mostSigBits >> 32, 8)).append("-").append(
				digits(mostSigBits >> 16, 4)).append("-").append(digits(mostSigBits, 4)).append("-").append(
				digits(leastSigBits >> 48, 4)).append("-").append(digits(leastSigBits, 12)).toString();
	}

	public int variant() {
		if (variant < 0)
			if (leastSigBits >>> 63 == 0L)
				variant = 0;
			else if (leastSigBits >>> 62 == 2L)
				variant = 2;
			else
				variant = (int) (leastSigBits >>> 61);
		return variant;
	}

	public int version() {
		if (version < 0)
			version = (int) (mostSigBits >> 12 & 15L);
		return version;
	}

}
